import asyncio
import logging
import random
import math
from pymodbus.server import StartAsyncTcpServer
from pymodbus.datastore import ModbusSequentialDataBlock, ModbusSparseDataBlock
from pymodbus.datastore import ModbusSlaveContext, ModbusServerContext
from pymodbus.device import ModbusDeviceIdentification

# 服务器监听的 IP 和端口
HOST = "0.0.0.0"  # 监听所有网络接/口/
# HOST = "192.168.1.234"  # 监听所有网络接口
# HOST = "192.168.3.234"  # 监听所有网络接口
# HOST = "172.16.11.201"  # 监听所有网络接/口
PORT = 502  # Modbus TCP 标准端口

# 设置日志
logging.basicConfig(
    format="%(asctime)s [%(levelname)s] %(message)s",
    level=logging.DEBUG,  # 调整日志级别为 DEBUG 以查看所有通信细节
)

# 启用 pymodbus 的通信日志
logging.getLogger("pymodbus").setLevel(logging.DEBUG)
logging.getLogger("pymodbus.server").setLevel(logging.DEBUG)
logging.getLogger("pymodbus.protocol").setLevel(logging.DEBUG)
logging.getLogger("pymodbus.transaction").setLevel(logging.DEBUG)
logging.getLogger("pymodbus.datastore").setLevel(logging.DEBUG)


class DynamicDataBlock(ModbusSequentialDataBlock):
    """动态数据块，支持多种寄存器类型的动态变化"""

    def __init__(self, address, size, reg_type, initial_offset=0):
        self.reg_type = reg_type  # 'coil', 'discrete', 'holding', 'input'
        if reg_type in ["coil", "discrete"]:
            # 线圈和离散输入使用布尔值
            self.values = [bool((initial_offset + i) % 2) for i in range(size)]
        else:
            # 寄存器和输入寄存器使用整数值
            self.values = [(initial_offset + i) % 65536 for i in range(size)]

        super().__init__(address, self.values.copy())
        self.counter = 0
        self.size = size
        self.address = address

    def update_values(self):
        """根据寄存器类型更新所有值"""
        self.counter += 1

        if self.reg_type == "coil":
            # 线圈值随机翻转
            for i in range(self.size):
                if random.random() > 0.8:  # 20%概率翻转
                    self.values[i] = not self.values[i]

        elif self.reg_type == "discrete":
            # 离散输入模拟开关量输入
            for i in range(self.size):
                # 模拟周期性变化
                self.values[i] = (i + self.counter) % 10 < 5

        elif self.reg_type == "holding":
            # 保持寄存器模拟可写寄存器
            for i in range(self.size):
                # 基础值 + offset
                offset = random.randint(-50, 50)  # ±0.5度
                self.values[i] = int(self.values[i] + offset) % 65536

        elif self.reg_type == "input":
            # 输入寄存器模拟只读传感器数据
            for i in range(self.size):
                # 基础值 + offset
                offset = random.randint(-50, 50)  # ±0.5度
                self.values[i] = (self.values[i] + offset) % 65536

        # 更新底层数据块
        if self.reg_type in ["coil", "discrete"]:
            self.setValues(self.address, [int(v) for v in self.values])
        else:
            self.setValues(self.address, self.values)


class DynamicSlaveContext(ModbusSlaveContext):
    """支持所有寄存器类型动态更新的从站上下文"""

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.update_task = None

    async def start_updates(self, interval=1.0):
        """启动后台更新任务"""

        async def update_loop():
            while True:
                try:
                    # 更新所有数据块
                    for reg_type, block in self.store.items():
                        if hasattr(block, "update_values"):
                            block.update_values()

                    await asyncio.sleep(interval)
                except Exception as e:
                    logging.error(f"Update error: {e}")
                    await asyncio.sleep(interval)

        self.update_task = asyncio.create_task(update_loop())


# 创建数据存储
def create_datastore(slave_id):
    store = DynamicSlaveContext(
        di=DynamicDataBlock(1, 100, "discrete", slave_id * 10000),  # 离散输入
        co=DynamicDataBlock(1, 100, "coil", slave_id * 10000),  # 线圈
        hr=DynamicDataBlock(1, 100, "holding", slave_id * 10000),  # 保持寄存器
        ir=DynamicDataBlock(1, 100, "input", slave_id * 10000),  # 输入寄存器
    )
    return store


# 设备标识信息
identity = ModbusDeviceIdentification()
identity.VendorName = "FullDynamicServer"
identity.ProductCode = "FDS-1"
identity.ModelName = "Fully Dynamic Modbus Server"


async def run_server():

    multi_store = {}
    for i in range(1, 7):
        multi_store[i] = create_datastore(i)
    context = ModbusServerContext(slaves=multi_store, single=False)
    for i in range(1, 7):
        await multi_store[i].start_updates(0.5)

    # 启动服务器
    server = await StartAsyncTcpServer(
        context=context, identity=identity, address=(HOST, PORT)
    )

    logging.info(f"Starting Modbus TCP Server on {HOST}:{PORT}...")
    logging.info("All register types are now dynamically updating:")
    logging.info("- Coils: Randomly flipping (20% chance per update)")
    logging.info("- Discrete Inputs: Simulating periodic on/off patterns")
    logging.info("- Holding Registers: Sine wave patterns")
    logging.info("- Input Registers: Simulating temperature sensors with noise")

    try:
        await server.serve_forever()
    except asyncio.CancelledError:
        server.close()
        await server.wait_closed()


if __name__ == "__main__":
    try:
        asyncio.run(run_server())
    except KeyboardInterrupt:
        logging.info("Server shutdown")
